﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using Mindfor.Web.Data;
using NHibernate;
using NHibernate.Linq;

namespace Mindfor.Web
{
	/// <summary>
	/// Provides functions to find add modules.
	/// </summary>
	public class ModuleFactory
	{
		static ModuleFactory s_default;

		/// <summary>
		/// Gets default module factory.
		/// </summary>
		public static ModuleFactory Default
		{
			get
			{
				if (s_default == null)
					s_default = new ModuleFactory();
				return s_default;
			}
		}

		List<ModuleBase> m_modules;
		
		/// <summary>
		/// Gets found modules.
		/// </summary>
		public ModuleCollection Modules { get; private set; }

		/// <summary>
		/// Initializes factory with modules from current AppDomain.
		/// </summary>
		public ModuleFactory()
		{
			m_modules = GetModulesFromCurrentDomain().ToList();
			Modules = new ModuleCollection(m_modules);
		}

		/// <summary>
		/// Searches modules in current AppDomain.
		/// </summary>
		private IEnumerable<ModuleBase> GetModulesFromCurrentDomain()
		{
			AppDomain domain = AppDomain.CurrentDomain;
			Type moduleBaseType = typeof(ModuleBase);

			// search all assemblies
			foreach (Assembly asm in domain.GetAssemblies())
			{
				// find modules
				foreach (Type type in asm.GetTypes())
				{
					if (type.IsSubclassOf(moduleBaseType))
					{
						ModuleBase module = (ModuleBase)Activator.CreateInstance(type);
						yield return module;
					}
				}
			}
		}

		/// <summary>
		/// Loads modules IsEnabled from data provider.
		/// </summary>
		/// <param name="data">Data session.</param>
		public void LoadIsEnabled(ISession data)
		{
			List<ModuleDb> modulesDb = data.Query<ModuleDb>().ToList();
			foreach (ModuleBase module in ModuleFactory.Default.Modules)
			{
				ModuleDb dbItem = modulesDb.FirstOrDefault(r => r.Name == module.Name);
				if (dbItem != null)
					module.IsEnabled = dbItem.IsEnabled;
			}
		}

		/// <summary>
		/// Saves modules IsEnabled to data provider.
		/// </summary>
		/// <param name="data">Data session.</param>
		public void SaveIsEnabled(ISession data)
		{
			using (ITransaction t = data.BeginTransaction())
			{
				List<ModuleDb> modulesDb = data.Query<ModuleDb>().ToList();
				foreach (ModuleBase module in ModuleFactory.Default.Modules)
				{
					ModuleDb dbItem = modulesDb.FirstOrDefault(r => r.Name == module.Name);
					if (dbItem == null)
					{
						dbItem = new ModuleDb(module.Name);
					}

					dbItem.IsEnabled = module.IsEnabled;
					data.SaveOrUpdate(dbItem);
				}

				t.Commit();
			}
		}

		/// <summary>
		/// Registers enabled modules.
		/// </summary>
		/// <param name="data">Data session to use.</param>
		public void RegisterEnabledModules(ISession data)
		{
			// register
			List<ModuleBase> registered = new List<ModuleBase>();
			ModuleRegistrationData registrationData = new ModuleRegistrationData(data, RouteTable.Routes, GlobalFilters.Filters);
			foreach (ModuleBase m in Modules.GetEnabledModules())
			{
				try
				{
					m.Register(registrationData);
					registered.Add(m);
				}
				catch (Exception ex)
				{
					m.State = ModuleState.Error;
					m.RegistrationError = ex;
				}
			}
			
			// call after register
			ModuleBase[] registeredArray = registered.ToArray();
			foreach (ModuleBase m in registered)
			{
				try
				{
					m.AfterRegister(registrationData, registeredArray);
					m.State = ModuleState.Registered;
				}
				catch (Exception ex)
				{
					m.State = ModuleState.Error;
					m.RegistrationError = ex;
				}
			}
		}

		/// <summary>
		/// Unloads enabled modules.
		/// </summary>
		public void UnloadRegisteredModules()
		{
			foreach (ModuleBase m in Modules.GetRegisteredModules())
				m.Unload();
		}
	}
}
